/* tinyproxy - A fast light-weight HTTP proxy
 * Copyright (C) 2004 Robert James Kaes <rjkaes@users.sourceforge.net>
 * Copyright (C) 2009 Michael Adam <obnox@samba.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/* Parses the configuration file and sets up the config_s structure for
 * use by the application.  This file replaces the old grammar.y and
 * scanner.l files.  It takes up less space and _I_ think is easier to
 * add new directives to.  Who knows if I'm right though.
 */

#include "conf.h"
#include "child.h"
#include "heap.h"
#include "html-error.h"
#include "log.h"
#include "reqs.h"

/*
 * The configuration directives are defined in the structure below.  Each
 * directive requires a regular expression to match against, and a
 * function to call when the regex is matched.
 *
 * Below are defined certain constant regular expression strings that
 * can (and likely should) be used when building the regex for the
 * given directive.
 */
#define WS "[[:space:]]+"
#define STR "\"([^\"]+)\""
#define BOOL "(yes|on|no|off)"
#define INT "((0x)?[[:digit:]]+)"
#define ALNUM "([-a-z0-9._]+)"
#define IP "((([0-9]{1,3})\\.){3}[0-9]{1,3})"
#define IPMASK "(" IP "(/[[:digit:]]+)?)"
#define BEGIN "^[[:space:]]*"
#define END "[[:space:]]*$"

/*
 * Limit the maximum number of substring matches to a reasonably high
 * number.  Given the usual structure of the configuration file, sixteen
 * substring matches should be plenty.
 */
#define RE_MAX_MATCHES 16

/*
 * All configuration handling functions are REQUIRED to be defined
 * with the same function template as below.
 */
typedef int
(*CONFFILE_HANDLER)(struct config_s *, const char *, regmatch_t[]);

/*
 * Define the pattern used by any directive handling function.  The
 * following arguments are defined:
 *
 *   struct config_s* conf   pointer to the current configuration structure
 *   const char* line          full line matched by the regular expression
 *   regmatch_t match[]        offsets to the substrings matched
 *
 * The handling function must return 0 if the directive was processed
 * properly.  Any errors are reported by returning a non-zero value.
 */
#define HANDLE_FUNC(func) \
  int func(struct config_s* conf, const char* line, \
           regmatch_t match[])

/*
 * List all the handling functions.  These are defined later, but they need
 * to be in-scope before the big structure below.
 */
static HANDLE_FUNC (handle_nop) {
	return 0;
} /* do nothing function */

static HANDLE_FUNC (handle_bind);
static HANDLE_FUNC (handle_bindsame);
static HANDLE_FUNC (handle_defaulterrorfile);
static HANDLE_FUNC (handle_errorfile);

static HANDLE_FUNC (handle_group);
static HANDLE_FUNC (handle_listen);
static HANDLE_FUNC (handle_logfile);
static HANDLE_FUNC (handle_loglevel);
static HANDLE_FUNC (handle_maxclients);
static HANDLE_FUNC (handle_maxrequestsperchild);
static HANDLE_FUNC (handle_maxspareservers);
static HANDLE_FUNC (handle_minspareservers);
static HANDLE_FUNC (handle_pidfile);
static HANDLE_FUNC (handle_port);

static HANDLE_FUNC (handle_startservers);
static HANDLE_FUNC (handle_syslog);
static HANDLE_FUNC (handle_timeout);

static HANDLE_FUNC (handle_user);

static void
config_free_regex(void);

/*
 * This macro can be used to make standard directives in the form:
 *   directive arguments [arguments ...]
 *
 * The directive itself will be the first matched substring.
 *
 * Note that this macro is not required.  As you can see below, the
 * comment and blank line elements are defined explicitly since they
 * do not follow the pattern above.  This macro is for convenience
 * only.
 */
#define STDCONF(d, re, func) { BEGIN "(" d ")" WS re END, func, NULL }

/*
 * Holds the regular expression used to match the configuration directive,
 * the function pointer to the routine to handle the directive, and
 * for internal use, a pointer to the compiled regex so it only needs
 * to be compiled one.
 */
struct {
	const char *re;
	CONFFILE_HANDLER handler;
	regex_t *cre;
} directives[] = {
/* comments */
{
BEGIN "#", handle_nop, NULL },
/* blank lines */
{ "^[[:space:]]+$", handle_nop, NULL },
/* string arguments */
STDCONF ("logfile", STR, handle_logfile),
STDCONF ("pidfile", STR, handle_pidfile),
STDCONF ("defaulterrorfile", STR, handle_defaulterrorfile),
/* boolean arguments */
STDCONF ("syslog", BOOL, handle_syslog),
STDCONF ("bindsame", BOOL, handle_bindsame),
/* integer arguments */
STDCONF ("port", INT, handle_port),
STDCONF ("maxclients", INT, handle_maxclients),
STDCONF ("maxspareservers", INT, handle_maxspareservers),
STDCONF ("minspareservers", INT, handle_minspareservers),
STDCONF ("startservers", INT, handle_startservers),
STDCONF ("maxrequestsperchild", INT, handle_maxrequestsperchild),
STDCONF ("timeout", INT, handle_timeout),
/* alphanumeric arguments */
STDCONF ("user", ALNUM, handle_user),
STDCONF ("group", ALNUM, handle_group),
/* ip arguments */
STDCONF ("listen", "(" IP ")", handle_listen),
STDCONF ("bind", "(" IP ")", handle_bind),
/* other */
STDCONF ("errorfile", INT WS STR, handle_errorfile),

/* loglevel */
STDCONF ("loglevel", "(critical|error|warning|notice|connect|info)",
		handle_loglevel) };

const unsigned int ndirectives = sizeof(directives) / sizeof(directives[0]);

static void free_config(struct config_s *conf) {
	safefree(conf->config_file);
	safefree(conf->logf_name);
	safefree(conf->user);
	safefree(conf->group);
	vector_delete(conf->listen_addrs);
	safefree(conf->pidpath);
	safefree(conf->bind_address);
	hashmap_delete(conf->errorpages);
	safefree(conf->errorpage_undef);

	memset(conf, 0, sizeof(*conf));
}

/*
 * Compiles the regular expressions used by the configuration file.  This
 * routine MUST be called before trying to parse the configuration file.
 *
 * Returns 0 on success; negative upon failure.
 */
int config_compile_regex(void) {
	unsigned int i, r;

	for (i = 0; i != ndirectives; ++i) {
		assert(directives[i].handler);
		assert(!directives[i].cre);

		directives[i].cre = (regex_t *) safemalloc(sizeof(regex_t));
		if (!directives[i].cre)
			return -1;

		r = regcomp(directives[i].cre, directives[i].re, REG_EXTENDED | REG_ICASE | REG_NEWLINE);
		if (r)
			return r;
	}

	atexit(config_free_regex);

	return 0;
}

/*
 * Frees pre-compiled regular expressions used by the configuration
 * file. This function is registered to be automatically called at exit.
 */
static void config_free_regex(void) {
	unsigned int i;

	for (i = 0; i < ndirectives; i++) {
		if (directives[i].cre) {
			regfree(directives[i].cre);
			safefree(directives[i].cre);
			directives[i].cre = NULL;
		}
	}
}

/*
 * Attempt to match the supplied line with any of the configuration
 * regexes defined above.  If a match is found, call the handler
 * function to process the directive.
 *
 * Returns 0 if a match was found and successfully processed; otherwise,
 * a negative number is returned.
 */
static int check_match(struct config_s *conf, const char *line) {
	regmatch_t match[RE_MAX_MATCHES];
	unsigned int i;

	assert(ndirectives > 0);

	for (i = 0; i != ndirectives; ++i) {
		assert(directives[i].cre);
		if (!regexec(directives[i].cre, line, RE_MAX_MATCHES, match, 0))
			return (*directives[i].handler)(conf, line, match);
	}

	return -1;
}

/*
 * Parse the previously opened configuration stream.
 */
static int config_parse(struct config_s *conf, FILE * f) {
	char buffer[1024]; /* 1KB lines should be plenty */
	unsigned long lineno = 1;

	while (fgets(buffer, sizeof(buffer), f)) {
		if (check_match(conf, buffer)) {
			printf("Syntax error on line %ld\n", lineno);
			return 1;
		}
		++lineno;
	}
	return 0;
}

/**
 * Read the settings from a config file.
 */
static int load_config_file(const char *config_fname, struct config_s *conf) {
	FILE *config_file;
	int ret = -1;

	config_file = fopen(config_fname, "r");
	if (!config_file) {
		fprintf(stderr, "%s: Could not open config file \"%s\".\n", PACKAGE, config_fname);
		goto done;
	}

	if (config_parse(conf, config_file)) {
		fprintf(stderr, "Unable to parse config file. "
				"Not starting.\n");
		goto done;
	}

	ret = 0;

	done: if (config_file)
		fclose(config_file);

	return ret;
}

static void initialize_with_defaults(struct config_s *conf, struct config_s *defaults) {
	if (defaults->logf_name) {
		conf->logf_name = safestrdup(defaults->logf_name);
	}

	if (defaults->config_file) {
		conf->config_file = safestrdup(defaults->config_file);
	}

	conf->syslog = defaults->syslog;
	conf->port = defaults->port;

	conf->godaemon = defaults->godaemon;
	conf->quit = defaults->quit;

	if (defaults->user) {
		conf->user = safestrdup(defaults->user);
	}

	if (defaults->group) {
		conf->group = safestrdup(defaults->group);
	}

	if (defaults->listen_addrs) {
		ssize_t i;

		conf->listen_addrs = vector_create();
		for (i = 0; i < vector_length(defaults->listen_addrs); i++) {
			char *addr;
			size_t size;
			addr = (char *) vector_getentry(defaults->listen_addrs, i, &size);
			vector_append(conf->listen_addrs, addr, size);
		}

	}

	if (defaults->pidpath) {
		conf->pidpath = safestrdup(defaults->pidpath);
	}

	conf->idletimeout = defaults->idletimeout;

	if (defaults->bind_address) {
		conf->bind_address = safestrdup(defaults->bind_address);
	}

	conf->bindsame = defaults->bindsame;

	if (defaults->errorpage_undef) {
		conf->errorpage_undef = safestrdup(defaults->errorpage_undef);
	}

}

/**
 * Load the configuration.
 */
int reload_config_file(const char *config_fname, struct config_s *conf, struct config_s *defaults) {
	int ret;

	log_message(LOG_INFO, "Reloading config file");

	free_config(conf);

	initialize_with_defaults(conf, defaults);

	ret = load_config_file(config_fname, conf);
	if (ret != 0) {
		goto done;
	}

	/* Set the default values if they were not set in the config file. */
	if (conf->port == 0) {
		/*
		 * Don't log here in error path:
		 * logging might not be set up yet!
		 */
		fprintf (stderr, PACKAGE ": You MUST set a Port in the config file.\n");
		ret = -1;
		goto done;
	}

	if (!conf->user) {
		log_message(LOG_WARNING, "You SHOULD set a UserName in the "
				"config file. Using current user instead.");
	}

	if (conf->idletimeout == 0) {
		conf->idletimeout = 600;
	}

	done: return ret;
}

/***********************************************************************
 *
 * The following are basic data extraction building blocks that can
 * be used to simplify the parsing of a directive.
 *
 ***********************************************************************/

static char *
get_string_arg(const char *line, regmatch_t * match) {
	char *p;
	const unsigned int len = match->rm_eo - match->rm_so;

	assert(line);
	assert(len > 0);

	p = (char *) safemalloc(len + 1);
	if (!p)
		return NULL;

	memcpy(p, line + match->rm_so, len);
	p[len] = '\0';
	return p;
}

static int set_string_arg(char **var, const char *line, regmatch_t * match) {
	char *arg = get_string_arg(line, match);

	if (!arg)
		return -1;

	if (*var != NULL) {
		safefree(*var);
	}

	*var = arg;

	return 0;
}

static int get_bool_arg(const char *line, regmatch_t * match) {
	const char *p = line + match->rm_so;

	assert(line);
	assert(match && match->rm_so != -1);

	/* "y"es or o"n" map as true, otherwise it's false. */
	if (tolower(p[0]) == 'y' || tolower(p[1]) == 'n')
		return 1;
	else
		return 0;
}

static int set_bool_arg(unsigned int *var, const char *line, regmatch_t * match) {
	assert(var);
	assert(line);
	assert(match && match->rm_so != -1);

	*var = get_bool_arg(line, match);
	return 0;
}

static unsigned long get_long_arg(const char *line, regmatch_t * match) {
	assert(line);
	assert(match && match->rm_so != -1);

	return strtoul(line + match->rm_so, NULL, 0);
}

static int set_int_arg(unsigned int *var, const char *line, regmatch_t * match) {
	assert(var);
	assert(line);
	assert(match);

	*var = (unsigned int) get_long_arg(line, match);
	return 0;
}

/***********************************************************************
 *
 * Below are all the directive handling functions.  You will notice
 * that most of the directives delegate to one of the basic data
 * extraction routines.  This is deliberate.  To add a new directive
 * to tinyproxy only requires you to define the regular expression
 * above and then figure out what data extract routine to use.
 *
 * However, you will also notice that more complicated directives are
 * possible.  You can make your directive as complicated as you require
 * to express a solution to the problem you're tackling.
 *
 * See the definition/comment about the HANDLE_FUNC() macro to learn
 * what arguments are supplied to the handler, and to determine what
 * values to return.
 *
 ***********************************************************************/

static HANDLE_FUNC (handle_logfile) {
	return set_string_arg(&conf->logf_name, line, &match[2]);
}

static HANDLE_FUNC (handle_pidfile) {
	return set_string_arg(&conf->pidpath, line, &match[2]);
}

static HANDLE_FUNC (handle_defaulterrorfile) {
	return set_string_arg(&conf->errorpage_undef, line, &match[2]);
}

static HANDLE_FUNC (handle_syslog) {
	fprintf(stderr, "Syslog support not compiled in executable.\n");
	return 1;
}

static HANDLE_FUNC (handle_bindsame) {
	int r = set_bool_arg(&conf->bindsame, line, &match[2]);

	if (r)
		return r;
	log_message(LOG_INFO, "Binding outgoing connection to incoming IP");
	return 0;
}

static HANDLE_FUNC (handle_port) {
	set_int_arg(&conf->port, line, &match[2]);

	if (conf->port > 65535) {
		fprintf(stderr, "Bad port number (%d) supplied for Port.\n", conf->port);
		return 1;
	}

	return 0;
}

static HANDLE_FUNC (handle_maxclients) {
	child_configure(CHILD_MAXCLIENTS, get_long_arg(line, &match[2]));
	return 0;
}

static HANDLE_FUNC (handle_maxspareservers) {
	child_configure(CHILD_MAXSPARESERVERS, get_long_arg(line, &match[2]));
	return 0;
}

static HANDLE_FUNC (handle_minspareservers) {
	child_configure(CHILD_MINSPARESERVERS, get_long_arg(line, &match[2]));
	return 0;
}

static HANDLE_FUNC (handle_startservers) {
	child_configure(CHILD_STARTSERVERS, get_long_arg(line, &match[2]));
	return 0;
}

static HANDLE_FUNC (handle_maxrequestsperchild) {
	child_configure(CHILD_MAXREQUESTSPERCHILD, get_long_arg(line, &match[2]));
	return 0;
}

static HANDLE_FUNC (handle_timeout) {
	return set_int_arg(&conf->idletimeout, line, &match[2]);
}

static HANDLE_FUNC (handle_user) {
	return set_string_arg(&conf->user, line, &match[2]);
}

static HANDLE_FUNC (handle_group) {
	return set_string_arg(&conf->group, line, &match[2]);
}

static HANDLE_FUNC (handle_bind) {
	fprintf(stderr, "\"Bind\" cannot be used with transparent support enabled.\n");
	return 1;
}

static HANDLE_FUNC (handle_listen) {
	char *arg = get_string_arg(line, &match[2]);

	if (arg == NULL) {
		return -1;
	}

	if (conf->listen_addrs == NULL) {
		conf->listen_addrs = vector_create();
		if (conf->listen_addrs == NULL) {
			log_message(LOG_WARNING, "Could not create a list "
					"of listen addresses.");
			safefree(arg);
			return -1;
		}
	}

	vector_append(conf->listen_addrs, arg, strlen(arg) + 1);

	log_message(LOG_INFO, "Added address [%s] to listen addresses.", arg);

	safefree(arg);
	return 0;
}

static HANDLE_FUNC (handle_errorfile) {
	/*
	 * Because an integer is defined as ((0x)?[[:digit:]]+) _two_
	 * match places are used.  match[2] matches the full digit
	 * string, while match[3] matches only the "0x" part if
	 * present.  This is why the "string" is located at
	 * match[4] (rather than the more intuitive match[3].
	 */
	unsigned long int err = get_long_arg(line, &match[2]);
	char *page = get_string_arg(line, &match[4]);

	add_new_errorpage(page, err);
	safefree(page);
	return 0;
}

/*
 * Log level's strings.

 */
struct log_levels_s {
	const char *string;
	int level;
};
static struct log_levels_s log_levels[] = { { "critical", LOG_CRIT }, { "error",
LOG_ERR }, { "warning", LOG_WARNING }, { "notice", LOG_NOTICE }, { "connect", LOG_CONN }, { "info", LOG_INFO } };

static HANDLE_FUNC (handle_loglevel) {
	static const unsigned int nlevels = sizeof(log_levels) / sizeof(log_levels[0]);
	unsigned int i;

	char *arg = get_string_arg(line, &match[2]);

	for (i = 0; i != nlevels; ++i) {
		if (!strcasecmp(arg, log_levels[i].string)) {
			set_log_level(log_levels[i].level);
			safefree(arg);
			return 0;
		}
	}

	safefree(arg);
	return -1;
}
